<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    
</body>
</html>
<script>
    // async函数返回一个Promise对象 可以使用then方法添加回调函数 
    // 当函数执行的时候 一旦遇到await就会先返回等到异步操作完成 再接着执行函数体内后面的语句

    // 语法
    // async函数的语法规则总体上比较简单 难点是错误处理机制
    // 返回Promise对象
    // async函数返回一个Promise对象
    // async函数内部return语句返回的值 会成为then方法回调函数的参数
    async function f() {
        return 'hello world';
    }

    f().then(v => console.log(v))
    // "hello world"


    // async函数内部抛出错误 会导致返回的 Promise 对象变为reject状态
    // 抛出的错误对象会被catch方法回调函数接收到

    async function f() {
        throw new Error('出错了');
    }

    f().then(
        v => console.log(v),
        e => console.log(e)
    )
    // Error: 出错了


    // Promise对象状态的变化
    // async函数返回的 Promise 对象 必须等到内部所有await命令后面的 Promise 对象执行完 才会发生状态改变 
    // 除非遇到return语句或者抛出错误 也就是说 只有async函数内部的异步操作执行完 才会执行then方法指定的回调函数
    

    async function getTitle(url) {
        let response = await fetch(url);
        let html = await response.text();
        return html.match(/<title>([\s\S]+)<\/title>/i)[1];
    }
    getTitle('https://tc39.github.io/ecma262/').then(console.log)
    // "ECMAScript 2017 Language Specification"

    // 上面代码中 函数getTitle内部有三个操作 抓取网页、取出文本、匹配页面标题
    // 只有这三个操作全部完成 才会执行then方法里面的console.log


    // await命令
    // 正常情况下 await命令后面是一个 Promise 对象 返回该对象的结果 如果不是 Promise 对象 就直接返回对应的值
    async function f() {
        // 等同于
        // return 123;
        return await 123;
    }

    f().then(v => console.log(v))
    // 123


    // 上面代码中 await命令的参数是数值123 这时等同于return 123


    // 另一种情况是 await命令后面是一个thenable对象（即定义then方法的对象）那么await会将其等同于 Promise 对象



    // 错误处理
    // 如果await后面的异步操作出错，那么等同于async函数返回的 Promise 对象被reject
    async function f() {
        await new Promise(function (resolve, reject) {
            throw new Error('出错了');
        });
    }

    f()
    .then(v => console.log(v))
    .catch(e => console.log(e))
    // Error：出错了


    // 异步处理的比较
    // 假定某个 DOM 元素上面 部署了一系列的动画 前一个动画结束 才能开始后一个
    // 如果当中有一个动画出错 就不再往下执行 返回上一个成功执行的动画的返回值

    // Promise的写法
    // 虽然 Promise 的写法比回调函数的写法大大改进 但是一眼看上去 代码完全都是 Promise 的 API（then、catch等等）
    // 操作本身的语义反而不容易看出来
    function chainAnimationsPromise(elem, animations) {
        // 变量ret用来保存上一个动画的返回值
        let ret = null;
        // 新建一个空的Promise
        let p = Promise.resolve();
        // 使用then方法，添加所有动画
        for(let anim of animations) {
            p = p.then(function(val) {
                ret = val;
                return anim(elem);
            });
        }
        // 返回一个部署了错误捕捉机制的Promise
        return p.catch(function(e) {
            /* 忽略错误，继续执行 */
        }).then(function() {
            return ret;
        });
    }

    // Generator的写法
    // 上面代码使用 Generator 函数遍历了每个动画 语义比 Promise 写法更清晰 用户定义的操作全部都出现在spawn函数的内部
    // 这个写法的问题在于 必须有一个任务运行器 自动执行 Generator 函数 
    // spawn函数就是自动执行器 它返回一个 Promise 对象 而且必须保证yield语句后面的表达式 必须返回一个 Promise
    function chainAnimationsGenerator(elem, animations) {
        return spawn(function*() {
            let ret = null;
            try {
                for(let anim of animations) {
                    ret = yield anim(elem);
                }
            } catch(e) {
                /* 忽略错误，继续执行 */
            }
            return ret;
        });
    }


    // async的写法
    // 可以看到 Async 函数的实现最简洁 最符合语义 几乎没有语义不相关的代码
    // 它将 Generator 写法中的自动执行器 改在语言层面提供 不暴露给用户 因此代码量最少
    // 如果使用 Generator 写法 自动执行器需要用户自己提供
    async function chainAnimationsAsync(elem, animations) {
        let ret = null;
        try {
            for(let anim of animations) {
                ret = await anim(elem);
            }
        } catch(e) {
            /* 忽略错误，继续执行 */
        }
        return ret;
    }




</script>